from communication.asyncServer import AsyncServer
from masterServer.comm.sql_cmd import increase_currency
from public.currency import Currency
from public.message import tic_tac_toe_message
from public.error_code import ErrorCode
import numpy as np
import random


class Victor(Exception):
    """胜利者"""

    def __init__(self, v):
        self.value = v


class Board:
    """棋盘"""

    def __init__(self, data=None):
        if data:
            self.data = np.array(data)
        else:
            self.data = np.zeros((3, 3), dtype=int)
        self.history = []

    def __iter__(self):
        return iter(self.data)

    def __str__(self):
        return '\n'.join(' '.join('□○×'[j] for j in i) for i in self.data)

    def check(self):
        """校验棋盘是否合法"""
        # 棋盘大小非法
        if self.data.shape != (3, 3):
            return False
        # 棋盘内容非法
        if np.any((self.data != 0) & (self.data != 1) & (self.data != 2)):
            return False
        # 棋子数量非法
        count1 = np.sum(self.data == 1)
        count2 = np.sum(self.data == 2)
        if count1 != count2 + 1:
            return False
        return True

    def judge(self):
        """判断胜负"""
        a = [
            *((i, ...) for i in range(3)),  # 每行
            *((..., i) for i in range(3)),  # 每列
            np.eye(3, dtype=bool),  # 斜线
            np.eye(3, dtype=bool)[::-1],  # 反斜线
        ]
        for i in a:
            if np.all(self.data[i] == 1):
                raise Victor(1)
            if np.all(self.data[i] == 2):
                raise Victor(2)
        if 0 not in self.data:
            raise Victor(0)

    def fall(self, n, player):
        """落子并判断胜负
        n：落子编号
        player：落子玩家（1：用户，2：AI）
        结果：是否成功落子，如果胜负已分则抛出Victor异常
        """
        n = int(n)
        pos = divmod(n - 1, 3)
        if self.data[pos] != 0:  # 落子位置不为空，落子失败
            return False
        self.data[pos] = player
        self.history.append(n)

        self.judge()
        return True


def ai(board):
    lines = (
        (0, ...), (1, ...), (2, ...),
        (..., 0), (..., 1), (..., 2),
        np.eye(3, dtype=bool), np.eye(3, dtype=bool)[::-1],
    )
    # 判断是否有快要成的棋，如果是自己就成，如果是敌人就堵
    for p in 2, 1:
        for i in lines:
            if sorted(board[i]) == [0, p, p]:
                board[i] = np.where(board[i] == 0, -1, p)
                x, y = np.argwhere(board == -1)[0]
                board[x, y] = 0
                return x * 3 + y + 1
    # 中间为空则落子中间
    if board[1, 1] == 0:
        return 5
    # 针对19套路，随机占边
    temp = np.array([[1, 0, 0], [0, 2, 0], [0, 0, 1]])
    if np.all(board == temp) or np.all(board == temp[::-1]):
        return random.choice([2, 4, 6, 8])
    # 针对24套路，堵两路的交点
    if len(board != 0) == 3:
        for i in 1, 3, 7, 9:
            x, y = divmod(i - 1, 3)
            if board[x, y] == 0 and np.any(board[x, :]) and np.any(board[:, y]):
                return i
    # 有角则占角
    if 0 in board[::2, ::2]:
        x, y = random.choice(np.argwhere(board[::2, ::2] == 0))
        return x * 2 * 3 + y * 2 + 1
    # 全部不满足就在空位置随机抽一个
    x, y = random.choice(np.argwhere(board == 0))
    return x * 3 + y + 1


@tic_tac_toe_message.on_receive
def tic_tac_toe(board):
    try:
        board = Board(board)
        assert board.check()
    except Exception:
        return ErrorCode.invalid_board, -1

    e = -1
    try:
        board.judge()
        e = int(ai(board.data))
        assert board.fall(e, 2)
        return ErrorCode.success, e
    except Victor as v:
        error_code = [
            ErrorCode.victor_tie,
            ErrorCode.victor_player,
            ErrorCode.victor_ai,
        ][v.value]
        return error_code, e


@AsyncServer().register('tic_tac_toe')
async def tic_tac_toe_async(send, recv):
    board = Board()
    msg = {}
    try:
        while p := int(await recv()):
            msg['board'] = board.data.tolist()
            msg['history'] = board.history
            if p == -1:  # 玩家主动结束游戏
                msg['victor'] = -1
                msg['message'] = '好吧，欢迎再来玩哦'
                await send(msg)
                return
            if 1 <= p <= 9:
                if board.fall(p, 1):  # 玩家落子
                    e = ai(board.data)  # AI计算
                    msg['next'] = int(e)
                    assert board.fall(e, 2)  # AI落子
                else:
                    msg['error'] = '这个位置已经有子了，好好看清楚呀！'
            else:
                msg['error'] = '请输入正确序号'
            msg['board'] = board.data.tolist()
            await send(msg)
    except Victor as v:
        msg['victor'] = v.value
        msg['message'] = [
            '看来势均力敌呢，欢迎再战哦。',
            '恭喜你赢了！想要再玩还可以找我哦。',
            '哼哼哼，愚蠢的人类怎么可能赢过无敌的小魅呢。不服欢迎再战。'
        ][v.value]
        msg['board'] = board.data.tolist()
        await send(msg)
        if qq := await recv():
            qq = int(qq)
            count = [5, 10000, 0][v.value]
            increase_currency(qq, Currency.coin, count)
        await send('')
